package com.sinan.core.connector.components;

import lombok.EqualsAndHashCode;
import lombok.Getter;

import java.util.*;

/**
 * 可变对象，定义一个属性
 *
 * @author pengpei
 */
@EqualsAndHashCode(of = "name")
public final class PropertyDescriptor implements Comparable<PropertyDescriptor> {

    @Getter
    private final String name;

    @Getter
    private final String displayName;

    @Getter
    private final String description;

    @Getter
    private final String defaultValue;

    @Getter
    private final List<AllowableValue> allowableValues;

    @Getter
    private final boolean required;

    @Getter
    private final boolean sensitive;

    @Getter
    private final Set<PropertyDependency> dependencies;

    @Getter
    private final List<Validator> validators;

    private PropertyDescriptor(final Builder builder) {
        this.displayName = builder.displayName == null ? builder.name : builder.displayName;
        this.name = builder.name;
        this.description = builder.description;
        this.defaultValue = builder.defaultValue;
        this.allowableValues = builder.allowableValues == null ? null : Collections.unmodifiableList(new ArrayList<>(builder.allowableValues));
        this.required = builder.required;
        this.sensitive = builder.sensitive;
        this.validators = List.copyOf(builder.validators);
        this.dependencies = builder.dependencies == null ? Collections.emptySet() : Collections.unmodifiableSet(new HashSet<>(builder.dependencies));
    }

    @Override
    public int compareTo(PropertyDescriptor o) {
        if (o == null) {
            return -1;
        }
        return getName().compareTo(o.getName());
    }

    public static Builder builder() {
        return new Builder();
    }

    public static final class Builder {

        private String name;

        private String displayName;

        private String description;

        private boolean required = false;

        private boolean sensitive = false;

        private String defaultValue;

        private List<AllowableValue> allowableValues;

        private Set<PropertyDependency> dependencies;

        private List<Validator> validators = new ArrayList<>();

        public Builder displayName(String displayName) {
            if (null != displayName) {
                this.displayName = displayName;
            }
            return this;
        }

        public Builder name(final String name) {
            if (null != name) {
                this.name = name;
            }
            return this;
        }

        public Builder description(final String description) {
            if (null != description) {
                this.description = description;
            }
            return this;
        }

        public Builder defaultValue(final String value) {
            if (null != value) {
                this.defaultValue = value;
            }
            return this;
        }

        public Builder allowableValues(final Set<String> values) {
            if (null != values) {
                this.allowableValues = new ArrayList<>();

                for (final String value : values) {
                    this.allowableValues.add(new AllowableValue(value, value));
                }
            }
            return this;
        }

        public <E extends Enum<E>> Builder allowableValues(final E[] values) {
            if (null != values) {
                this.allowableValues = new ArrayList<>();
                for (final E value : values) {
                    allowableValues.add(new AllowableValue(value.name(), value.name()));
                }
            }
            return this;
        }

        public <E extends Enum<E> & DescribedValue> Builder allowableValues(final Class<E> enumClass) {
            this.allowableValues = new ArrayList<>();
            for (E enumValue : enumClass.getEnumConstants()) {
                this.allowableValues.add(new AllowableValue(enumValue.getValue(), enumValue.getDisplayName(), enumValue.getDescription()));
            }
            return this;
        }

        public Builder allowableValues(final String... values) {
            if (null != values) {
                this.allowableValues = new ArrayList<>();
                for (final String value : values) {
                    allowableValues.add(new AllowableValue(value, value));
                }
            }
            return this;
        }

        public Builder required(final boolean required) {
            this.required = required;
            return this;
        }

        public Builder sensitive(final boolean sensitive) {
            this.sensitive = sensitive;
            return this;
        }

        private boolean isValueAllowed(final String value) {
            if (allowableValues == null || value == null) {
                return true;
            }

            for (final AllowableValue allowableValue : allowableValues) {
                if (allowableValue.getValue().equals(value)) {
                    return true;
                }
            }

            return false;
        }

        public Builder dependsOn(final PropertyDescriptor property, final AllowableValue... dependentValues) {
            if (dependencies == null) {
                dependencies = new HashSet<>();
            }

            if (dependentValues.length == 0) {
                dependencies.add(new PropertyDependency(property.getName(), property.getDisplayName()));
            } else {
                final Set<String> dependentValueSet = new HashSet<>();
                for (final AllowableValue value : dependentValues) {
                    dependentValueSet.add(value.getValue());
                }

                dependencies.add(new PropertyDependency(property.getName(), property.getDisplayName(), dependentValueSet));
            }
            return this;
        }

        public Builder addValidator(final Validator validator) {
            if (validator != null) {
                validators.add(validator);
            }
            return this;
        }

        public Builder clearValidators() {
            validators.clear();
            return this;
        }

        public PropertyDescriptor build() {
            if (name == null) {
                throw new IllegalStateException("Must specify a name");
            }
            if (!isValueAllowed(defaultValue)) {
                throw new IllegalStateException("Default value [" + defaultValue + "] is not in the set of allowable values");
            }
            return new PropertyDescriptor(this);
        }
    }
}
